/*****************************************************\
* Copyright (c) 2020 The game2d Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/game2d.git
* Email:          505544956@qq.com
* FileName:       ctrls/layout.cc
* Create Time:    2020/03/21 12:38:46
* Description:    /
\****************************************************/
#include "layout.h"

#include "utils/utils.h"

namespace g2d {
namespace ctrl {
  Layout::Layout(bool load_res_on_enter)
      : Control(load_res_on_enter) {}
  const Inset& Layout::inset() const { return inset_; }
  void Layout::set_inset(const Inset &inset) { inset_ = inset; }
  void Layout::set_attribute(const char *name, const char *value) {
    if (strcmp(name, "inset") == 0) set_inset(utils::parse_inset(value));
    else Control::set_attribute(name, value);
  }
  void Layout::update_control(Input &inp, Audio &aud) {
    Control *ctrl = 0;
    for (auto v : datas_) {
      if (!v->enabled()) continue;
      ctrl = dynamic_cast<Control*>(v);
      if (!ctrl) continue;
      ctrl->update_control(inp, aud);
    }
  }
  void Layout::update_layout() {
    Control *ctrl = 0;
    for (auto v : datas_) {
      if (!v->visible()) continue;
      ctrl = dynamic_cast<Control*>(v);
      if (!ctrl) continue;
      ctrl->set_rect(rect_);
      ctrl->update_layout();
    }
  }

  HorLayout::HorLayout(bool load_res_on_enter)
      : Layout(load_res_on_enter)
      , valign_(VerAlign::Top)
      , hpadding_(0.f) {}
  float HorLayout::padding() const { return hpadding_; }
  void HorLayout::set_padding(float padding) { hpadding_ = padding; }
  VerAlign HorLayout::align() const { return valign_; }
  void HorLayout::set_align(VerAlign align) { valign_ = align; }
  void HorLayout::set_attribute(const char *name, const char *value) {
    if (strcmp(name, "padding") == 0) set_padding(utils::parse_float(value));
    else if (strcmp(name, "align") == 0) set_align(utils::parse_valign(value));
    else Layout::set_attribute(name, value);
  }
  void HorLayout::update_layout() {
    if (count() <= 0) return;

    SDL_FRect rc = rect_;
    rc.x += inset_.l;
    rc.y += inset_.t;
    rc.w -= inset_.l + inset_.r;
    rc.h -= inset_.t + inset_.b;

    Control *ctrl = 0;
    int i = -1;
    int fixed_count = 0;
    int expand_count = 0;
    float fixed_width = 0;

    Size *items_sz = new Size[count()];
    for (auto v : datas_) {
      ++i;
      if (!v->visible()) continue;
      ctrl = dynamic_cast<Control*>(v);
      if (!ctrl) continue;
      items_sz[i] = ctrl->test_size();
      if (items_sz[i].w <= 0) {
        ++expand_count;
        continue;
      }
      fixed_width += items_sz[i].w;
      ++fixed_count;
    }

    float expand_width = 0.f;
    if (expand_count > 0)
      expand_width = (rc.w - fixed_width - (fixed_count+expand_count-1) * hpadding_) / expand_count;

    i = -1;
    float x = rc.x, y = rc.y;
    for (auto v : datas_) {
      ++i;
      if (!v->visible()) continue;
      ctrl = dynamic_cast<Control*>(v);
      if (!ctrl) continue;
      if (items_sz[i].w <= 0) items_sz[i].w = expand_width;
      if (items_sz[i].h <= 0) items_sz[i].h = rc.h;
      switch (valign_) {
        case VerAlign::Top: y = rc.y; break;
        case VerAlign::Middle: y = rc.y + (rc.h - items_sz[i].h)/2.f; break;
        case VerAlign::Bottom: y = rc.y + rc.h - items_sz[i].h; break;
      }
      ctrl->set_rect(x, y, items_sz[i].w, items_sz[i].h);
      ctrl->update_layout();
      x += items_sz[i].w + hpadding_;
    }

    delete[] items_sz;
  }

  VerLayout::VerLayout(bool load_res_on_enter)
      : Layout(load_res_on_enter)
      , halign_(HorAlign::Left)
      , vpadding_(0.f) {}
  float VerLayout::padding() const { return vpadding_; }
  void VerLayout::set_padding(float padding) { vpadding_ = padding; }
  HorAlign VerLayout::align() const { return halign_; }
  void VerLayout::set_align(HorAlign align) { halign_ = align; }
  void VerLayout::set_attribute(const char *name, const char *value) {
    if (strcmp(name, "padding") == 0) set_padding(utils::parse_float(value));
    else if (strcmp(name, "align") == 0) set_align(utils::parse_halign(value));
    else Layout::set_attribute(name, value);
  }
  void VerLayout::update_layout() {
    if (count() <= 0) return;

    SDL_FRect rc = rect_;
    rc.x += inset_.l;
    rc.y += inset_.t;
    rc.w -= inset_.l + inset_.r;
    rc.h -= inset_.t + inset_.b;

    Control *ctrl = 0;
    int i = -1;
    int fixed_count = 0;
    int expand_count = 0;
    float fixed_height = 0;

    Size *items_sz = new Size[count()];
    for (auto v : datas_) {
      ++i;
      if (!v->visible()) continue;
      ctrl = dynamic_cast<Control*>(v);
      if (!ctrl) continue;
      items_sz[i] = ctrl->test_size();
      if (items_sz[i].h <= 0) {
        ++expand_count;
        continue;
      }
      fixed_height += items_sz[i].h;
      ++fixed_count;
    }

    float expand_height = 0.f;
    if (expand_count > 0)
      expand_height = (rc.h - fixed_height - (fixed_count+expand_count-1) * vpadding_) / expand_count;

    i = -1;
    float x = rc.x, y = rc.y;
    for (auto v : datas_) {
      ++i;
      if (!v->visible()) continue;
      ctrl = dynamic_cast<Control*>(v);
      if (!ctrl) continue;
      if (items_sz[i].w <= 0) items_sz[i].w = rc.w;
      if (items_sz[i].h <= 0) items_sz[i].h = expand_height;
      switch (halign_) {
        case HorAlign::Left: x = rc.x; break;
        case HorAlign::Center: x = rc.x + (rc.w - items_sz[i].w)/2.f; break;
        case HorAlign::Right: x = rc.x + rc.w - items_sz[i].w; break;
      }
      ctrl->set_rect(x, y, items_sz[i].w, items_sz[i].h);
      ctrl->update_layout();
      y += items_sz[i].h + vpadding_;
    }

    delete[] items_sz;
  }

  TileLayout::TileLayout(bool load_res_on_enter)
      : Layout(load_res_on_enter)
      , hpadding_(0.f)
      , vpadding_(0.f)
      , cols_(0) {}
  float TileLayout::hpadding() const { return hpadding_; }
  void TileLayout::set_hpadding(float padding) { hpadding_ = padding; }
  float TileLayout::vpadding() const { return vpadding_; }
  void TileLayout::set_vpadding(float padding) { vpadding_ = padding; }
  void TileLayout::set_item_size(const Size &size) { item_size_ = size; }
  const Size& TileLayout::item_size() const { return item_size_; }
  Uint32 TileLayout::column() const { return cols_; }
  void TileLayout::set_column(Uint32 cols) { cols_ = cols; }
  void TileLayout::set_attribute(const char *name, const char *value) {
    if (strcmp(name, "hpadding") == 0) set_hpadding(utils::parse_float(value));
    else if (strcmp(name, "vpadding") == 0) set_vpadding(utils::parse_float(value));
    else if (strcmp(name, "cols") == 0) set_column(utils::parse_uint32(value));
    else if (strcmp(name, "itemsize") == 0) set_item_size(utils::parse_size(value));
    else Layout::set_attribute(name, value);
  }
  void TileLayout::update_layout() {
    if (item_size_.h <= 0) return;
    if (count() <= 0) return;

    SDL_FRect rc = rect_;
    rc.x += inset_.l;
    rc.y += inset_.t;
    rc.w -= inset_.l + inset_.r;
    rc.h -= inset_.t + inset_.b;

    Control *ctrl = 0;

    float ph = hpadding_;
    float pv = vpadding_;
    float x = rc.x, y = rc.y;
    if (item_size_.w > 0) {
      if (cols_ > 0) {
        ph = (rc.w - item_size_.w * cols_)/(cols_-1);
        if (ph < 0.f) ph = 0.f;
      }
      for (auto v : datas_) {
        if (!v->visible()) continue;
        ctrl = dynamic_cast<Control*>(v);
        if (!ctrl) continue;
        if (x + item_size_.w + ph > rc.x + rc.w) {
          x = rc.x;
          y += item_size_.h + pv;
        }
        ctrl->set_rect(x, y, item_size_.w, item_size_.h);
        ctrl->update_layout();
        x += item_size_.w + ph;
      }
    } else {
      Size sz;
      for (auto v : datas_) {
        if (!v->visible()) continue;
        ctrl = dynamic_cast<Control*>(v);
        if (!ctrl) continue;
        sz = ctrl->test_size();
        if (sz.w <= 0.f) sz.w = rc.w - ph;
        if (sz.h <= 0.f) sz.h = item_size_.h;
        if (x + sz.w + ph > rc.x + rc.w) {
          x = rc.x;
          y += sz.h + pv;
        }
        ctrl->set_rect(x, y, sz.w, sz.h);
        ctrl->update_layout();
        x += sz.w + ph;
      }
    }
  }

  TabLayout::TabLayout(bool load_res_on_enter)
      : Layout(load_res_on_enter) {}
  Uint32 TabLayout::selected_id() const { return sel_; }
  Control* TabLayout::selected_ctrl() const { return item_ctrl(sel_); }
  void TabLayout::select_item(Control *ctrl) { sel_ = index(ctrl); }
  void TabLayout::select_item(Uint32 idx) { sel_ = idx; }
  void TabLayout::set_attribute(const char *name, const char *value) {
    if (strcmp(name, "selectedid") == 0) select_item(utils::parse_uint32(value));
    else Layout::set_attribute(name, value);
  }
  void TabLayout::update_layout() {
    auto ctrl = selected_ctrl();
    if (ctrl) {
      ctrl->set_rect(rect_.x + inset_.l
          , rect_.y + inset_.t
          , rect_.w - inset_.l - inset_.r
          , rect_.h - inset_.t - inset_.b);
      ctrl->update_layout();
    }
  }
} // end namespace ctrl
} // end namespace g2d